package org.example.multi_sys_consist.service.impl;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.java.Log;
import org.example.multi_sys_consist.common.BusinessException;
import org.example.multi_sys_consist.constant.TransactionJobType;
import org.example.multi_sys_consist.mapper.TransactionJobMapper;
import org.example.multi_sys_consist.module.TransactionJobExecutor;
import org.example.multi_sys_consist.pojo.entity.DemoUser;
import org.example.multi_sys_consist.pojo.entity.TransactionJob;
import org.example.multi_sys_consist.service.CurrentService;
import org.example.multi_sys_consist.service.TransactionJobService;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.bind.annotation.PostMapping;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;


@Service
public abstract class TransactionJobServiceImpl implements TransactionJobService {
    private final Logger logger = LoggerFactory.getLogger(TransactionJobServiceImpl.class);
    @Autowired
    private TransactionJobMapper mapper;
    @Autowired
    private CurrentService currentService;
    @Autowired
    private RedissonClient redissonClient;

    private final Map<String, TransactionJobExecutor> executorMap = new HashMap<>();
    private final Map<Integer, Integer> retryInterval = new HashMap<>();

    {
        retryInterval.put(1, 1);
        retryInterval.put(2, 2);
        retryInterval.put(3, 5);
        retryInterval.put(4, 30);
        retryInterval.put(5, 60);
        retryInterval.put(6, 60 * 12);
        retryInterval.put(7, 60 * 24);
        retryInterval.put(8, 60 * 24 * 2);
    }

    @Autowired
    public void initExecutor(List<TransactionJobExecutor> executors) {
        executors.forEach(e -> executorMap.put(e.type().name(), e));
    }

    @Override
    public TransactionJob create(TransactionJobType type, JSONObject data) {
        if (!TransactionSynchronizationManager.isActualTransactionActive()) {
            throw new BusinessException("执行此方法时，必须在事务中");
        }

        TransactionJob job = new TransactionJob();
        job.setType(type.name());
        job.setData(data.toJSONString());
        job.setContext(JSON.toJSONString(currentService.currentUser()));
        job.setCreateTime(System.currentTimeMillis());
        mapper.add(job);

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                doExecute(job);
            }
        });

        return job;
    }

    /**
     * 定时任务：每分钟执行一次
     */
    @Override
    @Scheduled(cron = "0 0/1 * * * ?")
    public void findAndExecute() {
        int offset = 0;
        int limit = 100;
        while (true) {
            List<TransactionJob> jobs = mapper.selectList(offset, limit);
            if (jobs.isEmpty()) {
                return;
            }
            for (TransactionJob job : jobs) {
                //判断重试次数，重试时间间隔随着次数的增多而逐渐增大
                Integer re = job.getRetryTimes();
                if (re == 0) {
                    this.doExecute(job);
                }
                //超过8次都没有成功，就不再重试了
                if (re > 0 && re < 8) {
                    Integer interval = retryInterval.get(re);
                    if (System.currentTimeMillis() - job.getUpdateTime() > interval * 60 * 1000) {
                        this.doExecute(job);
                    }
                }
            }
            offset += limit;
        }
    }

    /**
     * 注意：这里一定不能加事务，否则会因为内层事务抛异常导致外层事务被回滚，从而无法记录下报错原因
     */
    private void doExecute(TransactionJob job) {
        Lock lock = getLock(job);
        if (lock.tryLock()) {
            try {
                //设置上下文，因为是异步执行的，所以可能需要执行前设置好当时的操作用户
                DemoUser user = JSON.parseObject(job.getContext(), DemoUser.class);
                currentService.setCurrentUser(user);
                try {
                    TransactionJobExecutor executor = executorMap.get(job.getType());
                    executor.execute(job);
                    mapper.updateSuccess(job.getId(), System.currentTimeMillis());
                } catch (Exception e) {
                    logger.error("error execute job:{}", job.getId(), e);
                    String errorMessage = ExceptionUtil.stacktraceToString(e, 10000);
                    mapper.updateError(job.getId(), errorMessage, System.currentTimeMillis());
                } finally {
                    currentService.clearAll();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private Lock getLock(TransactionJob job) {
        //使用redisson获取分布式锁
        //todo 按照自己业务的需求，生成唯一的key
        String key = "";
        return redissonClient.getLock(key);
    }

}
